import os
import subprocess
import time
import getpass
import uuid
from datetime import datetime
from os.path import expanduser

import requests
from absl import app, flags

from coin.exchange.binance.kr_rest.native_private_client import BinanceNativePrivateClient
from coin.exchange.binance_futures.kr_rest.native_private_client import BinanceFuturesNativePrivateClient
from cc.appcoin2.strategy.support.util import get_cmd_vault_key_copy


class MySpotClient(BinanceNativePrivateClient):
  def __init__(self, key_file):
    super().__init__(key_file=key_file)
  
  def univ_transfer(
      self,
      trsf_type,
      asset,
      amount
  ):
    method = 'POST'
    path = '/sapi/v1/asset/transfer'
    params = {
        'type': trsf_type,
        'asset': asset,
        'amount': amount,
        'recvWindow': self._recv_window,
        'timestamp': int(time.time() * 1000),
    }
    time.sleep(1)
    update = self._query(method, path, params)
    return update
  
  def query_margin_account(
    self
  ):
    method = 'GET'
    path = '/sapi/v1/margin/account'
    params = {
        'recvWindow': self._recv_window,
        'timestamp': int(time.time() * 1000),
    }
    update = self._query(method, path, params)
    return update
  
  def history(
      self,
      trsf_type,
  ):
    method = 'GET'
    path = '/sapi/v1/asset/transfer'
    params = {
        'type': trsf_type,
        'startTime': int((time.time() - 86400 * 5) * 1000),
        'size': 100,
        'recvWindow': self._recv_window,
        'timestamp': int(time.time() * 1000),
    }
    update = self._query(method, path, params)
    return update


def init_flags():
  flags.DEFINE_string('account', 'xunke18_swap', 'account name')
  flags.DEFINE_string('assets', 'ALL', 'specify sell assets')
  flags.DEFINE_bool('query_only', True, 'query only mode')
  flags.DEFINE_string('quote', 'USDT', 'denominated ccy, USDT or BUSD for now')
  flags.DEFINE_string('show_history', '', 'show transaction history from 5 days ago with limit 100 items')


def parse_coin_m_wallet(msg):
  bal_dict = dict()
  for ass in msg['assets']:
    bal_dict[ass['asset']] = float(ass['walletBalance'])
  return bal_dict


def parse_spot_wallet(msg):
  bal_dict = dict()
  for ass in msg['balances']:
    bal_dict[ass['asset']] = float(ass['free'])
  return bal_dict


def parse_cross_margin_wallet(msg):
  bal_dict = dict()
  for ass in msg['userAssets']:
    bal_dict[ass['asset']] = float(ass['netAsset'])
  return bal_dict


def get_balance(spot_npc, swap_npc):
  print("\n#####################")
  print("-- SPOT --")
  bal = spot_npc.query_account().msg
  spot_wallet = parse_spot_wallet(bal)
  for asset, balance in spot_wallet.items():
    if balance > 0:
      print(f"{asset}: {balance}")
  print("\n-- SWAP --")
  bal = swap_npc.query_account().msg
  coinm_wallet = parse_coin_m_wallet(bal)
  for asset, balance in coinm_wallet.items():
    if balance != 0.0:
      print(f"{asset}: {balance}")
  print("\n-- MARGIN --")
  bal = spot_npc.query_margin_account().msg
  margin_wallet = parse_cross_margin_wallet(bal)
  for asset, balance in margin_wallet.items():
    if balance > 0:
      print(f"{asset}: {balance}")
  print("######################\n")
  return spot_wallet, coinm_wallet


def print_history(histories):
  for row in histories:
    trans_ts = int(row['timestamp'] / 1000)
    utc_dt = datetime.utcfromtimestamp(trans_ts)
    row['timestamp'] = utc_dt.strftime("%Y-%m-%d %H:%m:%S")
    print(row)


def coin_spot_ops(key_file, symbol, amount, is_buy=False, stack=1.0):
  target_position = amount if is_buy else 0.0
  cmd = "GLOG_logtostderr=1 "\
  "../coin_binary/coin2-bazel-bin/cc/appcoin2/support/approach_target_position "\
  f"--mea=Spot.Binance.v1.snapshot --symbol={symbol} --key_path={key_file} "\
  f"--pass_bp=0 --lot_size={amount/stack} --target_position={target_position} "\
  "--stickiness=2 --auto_shutdown_sec=600 "
  print(cmd)
  os.system(cmd)


def coin_m_to_cross_wallet(msc, bsnpc, asset, quote_fiat, amount=None):
  _, coin_m_wallet = get_balance(msc, bsnpc)
  if asset not in coin_m_wallet.keys():
    print(f"No asset {asset}!")
    return
  if amount is None:
    amount = coin_m_wallet[asset]
  else:
    amount = min(amount, coin_m_wallet[asset])
  if amount <= 0.0:
    print("Can't do this")
    return
  print(f"Move {amount} {asset} from coinM to spot..")

  # move coin M -> spot
  trsf_msg = msc.univ_transfer("CMFUTURE_MAIN", asset, amount).msg
  print(trsf_msg)
  if 'tranId' not in trsf_msg:
    return
  get_balance(msc, bsnpc)

  # sell asset..
  print(f"\n Sell {amount} {asset} ..")
  key_file = msc._auth_queryer._auth._key.key_file
  trading_pair = f"{asset}-{quote_fiat}"
  coin_spot_ops(key_file, trading_pair, amount)
  spot_wallet, _ = get_balance(msc, bsnpc)

  # spot to cross margin wallet
  quote_amount = spot_wallet[quote_fiat]
  if quote_amount > 0.0:
    trsf_msg = msc.univ_transfer("MAIN_MARGIN", quote_fiat, quote_amount).msg
    print(trsf_msg)
    if 'tranId' not in trsf_msg:
      return
  get_balance(msc, bsnpc)


def cross_wallet_to_coin_m(msc, bsnpc, asset, amount, quote_fiat, move_margin=False):
  # cross margin wallet to spot
  get_balance(msc, bsnpc)
  if move_margin:
    # TODO(taekwon): 1000 usdt may be not enough
    trsf_msg = msc.univ_transfer("MARGIN_MAIN", quote_fiat, 3000).msg
    print(trsf_msg)
    if 'tranId' not in trsf_msg:
      return
    get_balance(msc, bsnpc)
  
  # buy asset
  print(f"\n Buy {amount} {asset} ..")
  trading_pair = f"{asset}-{quote_fiat}"
  key_file = msc._auth_queryer._auth._key.key_file
  coin_spot_ops(key_file, trading_pair, amount, is_buy=True)
  spot_wallet, _ = get_balance(msc, bsnpc)

  # rest quote to cross margin wallet
  quote_amount = spot_wallet[quote_fiat]
  if quote_amount > 0.0:
    trsf_msg = msc.univ_transfer("MAIN_MARGIN", quote_fiat, quote_amount).msg
    print(trsf_msg)
    if 'tranId' not in trsf_msg:
      return
  
  # spot to coin M wallet
  asset_amount = spot_wallet[asset]
  if asset_amount > 0.0:
    trsf_msg = msc.univ_transfer("MAIN_CMFUTURE", asset, asset_amount).msg
    print(trsf_msg)
    if 'tranId' not in trsf_msg:
      return
  get_balance(msc, bsnpc)


# for balance > 0
def sell_coin_m(msc, bsnpc, specified_coins, quote):
  _, coin_m_wallet = get_balance(msc, bsnpc)
  for asset, balance in coin_m_wallet.items():
    if balance <= 0.0 or asset == quote:
      continue
    if specified_coins[0] == "ALL" or asset in specified_coins:
      print(f"{asset}: {balance}")
      coin_m_to_cross_wallet(msc, bsnpc, asset, quote, balance)

  spot_wallet, _ = get_balance(msc, bsnpc)
  for asset, balance in spot_wallet.items():
    if balance > 0.0:
      print(f"spot to coin_m.. {asset}: {balance}")
      trsf_msg = msc.univ_transfer("MAIN_CMFUTURE", asset, balance).msg
      print(trsf_msg)


# for balance < 0
def buy_coin_m(msc, bsnpc, specified_coins, quote):
  _, coin_m_wallet = get_balance(msc, bsnpc)
  for asset, balance in coin_m_wallet.items():
    if balance >= 0.0 or asset == quote:
      continue
    if specified_coins[0] == "ALL" or asset in specified_coins:
      print(f"{asset}: {balance}")
      cross_wallet_to_coin_m(msc, bsnpc, asset, -balance, quote, move_margin=True)


def main(_):
  account = flags.FLAGS.account
  random_suffix = uuid.uuid4().hex[:15]
  key_file = f"/tmp/temp_key_{random_suffix}.json"
  vault_key_name = f"trade/{account}/binance/fut_trade_key.json"
  cmd = get_cmd_vault_key_copy(vault_key_name, key_file)
  output = subprocess.check_output(cmd, shell=True)
  msc = MySpotClient(key_file=key_file)
  bsnpc = BinanceFuturesNativePrivateClient(key_file=key_file, api_host='dapi.binance.com')

  spot_wallet, coin_m_wallet = get_balance(msc, bsnpc)
  if len(flags.FLAGS.show_history) > 0:
    trsf_type = flags.FLAGS.show_history
    print(f"Get history of {trsf_type}...")
    history_msg = msc.history(trsf_type).msg
    if 'rows' in history_msg:
      print_history(history_msg['rows'])
    return

  if flags.FLAGS.query_only:
    return
  
  specified_coins = flags.FLAGS.assets.split(",")
  quote = flags.FLAGS.quote
  print("Sell for Coin M balance > 0")
  sell_coin_m(msc, bsnpc, specified_coins, quote)
  print("Buy for Coin M balance < 0")
  buy_coin_m(msc, bsnpc, specified_coins, quote)

  # buy test
  # cross_wallet_to_coin_m(msc, bsnpc, "LTC", 1, move_margin=True)
  # cross_wallet_to_coin_m(msc, bsnpc, "ETH", 0.1, move_margin=True)

  # wallet move test
  # trsf_msg = msc.univ_transfer("MAIN_MARGIN", "USDT", 0.03102196).msg
  # print(trsf_msg)

if __name__ == '__main__':
  init_flags()
  app.run(main)

# Usage
# ./pyrunner python/experimental/taekwon/binance_coin_swap.py --account=xunke136_swap --query_only=False
# ./pyrunner python/experimental/taekwon/binance_coin_swap.py --account=xunke136_swap --query_only=False --assets=LTC,ETH
# ./pyrunner python/experimental/taekwon/binance_coin_swap.py --account=xunke136_swap --query_only=False --show_history=MAIN_MARGIN

